﻿#region License statement
// NJamb, a specification and delta-specification DSL
// Copyright (c) 2010-2011, Mark Knell
// Published under the MIT License; all other rights reserved
#endregion

#region using...
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using JetBrains.Annotations;
using NJamb.Specifications.Delta;
using Stile.Patterns.SelfDescribingPredicates;
using Stile.Types.Enumerables;
using Stile.Validation;
#endregion

namespace NJamb
{
	public class Experiment
	{
		public static Builder<TSubject> On<TSubject>(TSubject subject)
		{
			return new Builder<TSubject>(subject);
		}

		public class Builder<TSubject>
		{
			private readonly TSubject _subject;

			public Builder(TSubject subject)
			{
				_subject = subject;
			}

			public Experiment<TSubject> Expecting(IDeltaSpecification deltaSpecification, params IDeltaSpecification[] additional)
			{
				return new Experiment<TSubject>(_subject, deltaSpecification, additional);
			}
		}
	}

	public class Experiment<TSubject> : Experiment, IExperiment<TSubject>, ICollaboratingExperiment<TSubject>
	{
		private readonly List<IDeltaSpecification> _deltaSpecifications;
		private readonly TSubject _subject;

		public Experiment(TSubject subject, [NotNull] IDeltaSpecification deltaSpecification, params IDeltaSpecification[] additional)
		{
			_subject = subject;
			ValidateArgument.IsNotDefault(() => deltaSpecification);
			_deltaSpecifications = new List<IDeltaSpecification>(additional.Unshift(deltaSpecification));
		}

		public bool HasRun { get; private set; }

		public ReadOnlyCollection<IDeltaSpecification> Specifications
		{
			get { return _deltaSpecifications.ToReadOnly(); }
		}
		public TSubject Subject
		{
			get { return _subject; }
		}

		public bool Try(Action<TSubject> action, Action<string> onFail, Action<string> onSuccess = null)
		{
			if (action == null)
			{
				throw new ArgumentNullException("action");
			}
			if (onFail == null)
			{
				throw new ArgumentNullException("onFail");
			}

			bool okay = true;
			try
			{
				IList<IPreconditionEvaluation> evaluations = _deltaSpecifications.Select(x => x.EvaluatePrecondition()).ToList();
				foreach (IPreconditionEvaluation evaluation in evaluations)
				{
					IEvaluation before = evaluation.Evaluation;
					okay = before.Success; 
					if (okay)
					{
						if (onSuccess != null)
						{
							onSuccess.Invoke(before.Reason);
						}
					}
					else
					{
						onFail.Invoke("Precondition: " + Environment.NewLine + before.Reason);
						break;
					}
				}
				if (okay)
				{
					action.Invoke(_subject);
					foreach (IPreconditionEvaluation preconditionEvaluation in evaluations)
					{
						IEvaluation after = preconditionEvaluation.GetPostcondition().Evaluate();
						if (!after.Success)
						{
							okay = false;
							onFail.Invoke(after.Reason);
							break;
						}
					}
				}
			}
			finally
			{
				HasRun = true;
			}
			return okay;
		}
	}
}
